package tests.model_tests;

import java.io.IOException;

import org.junit.*;
import application.models.Observable;
import application.models.Observer;
import application.models.datalayer.Attribute;
import application.models.datalayer.Date;
import application.models.exceptions.InvalidAttributeException;
import application.models.exceptions.MissingDataException;
import application.models.models.AbstractModel;
import application.models.models.CustomerModel;
import static org.junit.Assert.*;

/**
 * Testing the CustomerModel interface.
 * 
 * @author Morten F. Therkildsen & Philip J. Rasmussen
 */
public class CustomerModel_test {
	private String attributeName = "first_name";
	private CustomerModel customerModel_one = new CustomerModel(), customerModel_two = new CustomerModel();
	private boolean observer1Fail, observer2Fail;

	/**
	 * In order to not change existing data in the data layer during testing. We have had to create test models to run the tests on, that are deleted after the test.
	 * This means that every test here will fail if the constructors of the models fail. And it assumes the delete() and save() method passes.
	 * 
	 * @throws InvalidAttributeException	If an attribute cannot be set
	 * @throws MissingDataException			If data is missing
	 */
	@Before
	public void setup() throws InvalidAttributeException, MissingDataException{

		testInitHelper(customerModel_one);
		testInitHelper(customerModel_two);
	}

	@Test
	public void test_getAttribute(){		

		// per default, a new model have all attributes set to null. And as such
		// should return a null value.
		CustomerModel model = new CustomerModel();
		try{
			String attributeValue = (String) model.getAttribute(attributeName);
			assertNull(attributeValue);
		} catch(InvalidAttributeException e){	fail("The entity does not contain an attributeName attribute.");
		}

		// the model does not contain an attribute names "test_attribute", and as
		// such, should throw an exception
		try{
			customerModel_one.getAttribute("test_attribute");
			fail("The entity contains an attribute named test_attribute");
		} catch(InvalidAttributeException e){ assertTrue(e instanceof InvalidAttributeException); // This is expected
		} catch(Exception e){ fail("Invalid attribute exception was not thrown for \"test_attribute\"");
		}

		// the model cannot contain an attribute with no name, and should throw an exception
		try{
			customerModel_one.getAttribute(null);
			fail("The entity contains a null attribute");
		} catch(InvalidAttributeException e){ assertTrue(e instanceof InvalidAttributeException); // This is expected
		} catch(Exception e ){  fail("Invalid attribute exception was not thrown for \"\"");}
	}


	@Test
	public void test_setAttribute(){

		//valid parameters, should not throw an exception
		try{
			customerModel_one.setAttribute(attributeName, "testPlate_one");
			// if we get the test has passed. 
		} catch(InvalidAttributeException e){ fail("The entity does not contain an attributeName attribute, or it's data type is not String");
		}

		//valid parameters, should not throw an exception
		try{
			customerModel_two.setAttribute(attributeName, null);
			// if we get here the test has passed
		} catch(InvalidAttributeException e){ fail("The entity does not contain an attributeName attribute, or it failed with a null value");
		}

		// valid first parameter, second parameter not matching the data type of the attribute referred to by the first parameter
		// should throw an exception
		try{
			customerModel_two.setAttribute(attributeName, new Integer(0));	
			fail("No exception was thrown for unequal data types String and Integer");
		} catch(InvalidAttributeException e){ assertTrue(e instanceof InvalidAttributeException); // This is expected
		}		

		// valid second parameter, invalid first parameter.
		// should throw an exception
		try{
			customerModel_two.setAttribute("test_plate", "testPlate_one");	
			fail("No exception was thrown for non existing attribute");
		} catch(InvalidAttributeException e){ assertTrue(e instanceof InvalidAttributeException); // This is expected
		}

		// valid second parameter, invalid first parameter
		// should throw an exception
		try{
			customerModel_two.setAttribute(null, null);	
			fail("No exception was thrown for a null attribute name");
		} catch(InvalidAttributeException e){ assertTrue(e instanceof InvalidAttributeException); // This is expected
		}

		// invalid parameters, should throw an exception
		try{
			customerModel_two.setAttribute("test_plate", new Integer(0));	
			fail("No exception was thrown for non existing attribute and null attribute name");
		} catch(InvalidAttributeException e){ assertTrue(e instanceof InvalidAttributeException); // This is expected
		}

		// invalid parameters, should throw an exception
		try{
			customerModel_two.setAttribute(null, new Integer(0));	
			fail("No exception was thrown for non existing attribute and null attribute name");
		} catch(InvalidAttributeException e){ assertTrue(e instanceof InvalidAttributeException); // This is expected
		}	

		// Below we test that the setters actually changed an attribute value. 
		// These assume the getAttribute() test passes, and as such, can be commented out
		try {
			assertEquals(customerModel_one.getAttribute(attributeName), "testPlate_one");
			assertEquals(customerModel_two.getAttribute(attributeName), null);	
		} catch (InvalidAttributeException e) { // If this happens, look at the test for the getAttribute() unit to find the cause.
		}
	}

	@Test
	public void test_getId(){	

		//test that an exception will be thrown for an unsaved model
		CustomerModel customerModel_three = new CustomerModel();		
		try{
			customerModel_three.getId();
			fail("NullPointerException was not thrown for id value of null");
		} catch(NullPointerException e){ assertTrue(e instanceof NullPointerException); // This is expected
		}		
	}

	@Test
	public void test_save() {

		CustomerModel model = new CustomerModel();

		// Test that an exception is thrown when saving a model with no attribute values.
		try {
			model.save();
		} catch (MissingDataException e) { assertTrue(e instanceof MissingDataException); // This is expected
		}

		// The following test require getAttribute and setAttribute to pass tests in 
		// order to be indicative of problems with the save() method.

		// Sets an attribute to a specific value, save the model, and then set the same attribute to a different value
		// This returned object from getAttribute should be unequal to the object defined before save is called
		try{

			String attributeValue_before = "testPlate";
			customerModel_one.setAttribute(attributeName, attributeValue_before);
			customerModel_one.save();
			customerModel_one.setAttribute(attributeName, "testPlate_after");
			String attributeValue_after = (String) customerModel_one.getAttribute(attributeName);
			assertFalse(attributeValue_before.equals(attributeValue_after));			
		} catch(InvalidAttributeException e){ fail("The entity does not contain an attributeName attribute.");
		} catch (MissingDataException e) { // null values are allowed for the other attributes
		}
	}

	@Test
	public void test_load(){

		// Invalid parameter. Should throw an exception
		try{
			CustomerModel.load(-1);
			fail("An exception was not thrown for a negative id value");
		}catch(IllegalArgumentException e){ assertTrue(e instanceof IllegalArgumentException); // This is expected	
		}

		// Invalid parameter. Should throw an exception
		try{
			CustomerModel.load(0);
			fail("An exception was not thrown for an id value of zero");
		}catch(IllegalArgumentException e){ assertTrue(e instanceof IllegalArgumentException); // This is expected	
		}

		// Invalid parameter. Should throw an exception
		try{
			CustomerModel.load((Integer) null);
			fail("An exception was not thrown for an id value of null");
		}catch(NullPointerException e){ assertTrue(e instanceof NullPointerException); // This is expected	
		}
		
		// The following three tests assume that the getId() unit functions properly. If
		// the test fails here, look for the cause in getId() first.
		
		// Valid parameter. Should not throw an exception
		try{
			CustomerModel.load(customerModel_one.getId());
		}catch(Exception e){ fail("An exception was thrown for a valid id value");		
		}

		// Test that two separate calls to load(1) return the same object reference
		assertEquals(CustomerModel.load(customerModel_one.getId()), CustomerModel.load(customerModel_one.getId()));

		// Test that two separate calls to load(int id) with two separate id values return different object reference.
		assertFalse(CustomerModel.load(customerModel_one.getId()).equals(CustomerModel.load(customerModel_two.getId())));
	}

	@Test
	public void delete() {

		// Tests that a newly created model cannot be deleted before it has been saved.
		CustomerModel model = new CustomerModel();
		model.delete();
		assertFalse(model.isDeleted());

		// Tests that a saved model does not show as deleted
		assertFalse(customerModel_one.isDeleted());

		// Test that a deleted model show as deleted
		customerModel_one.delete();
		assertTrue(customerModel_one.isDeleted());

		// The following test assumes that the save() unit test passes. If this test fails, look 
		// for a solution in the save() unit first.

		// Test that a deleted model can be resaved (undoing the deletion)
		try {
			customerModel_one.save();
			assertFalse(customerModel_one.isDeleted());
		} catch (MissingDataException e) { fail("The model is registered as deleted after a call to its save method");
		}
	}

	@Test
	public void test_observers() throws InvalidAttributeException, IOException, MissingDataException {

		// Test that an observer can be added. no exception should be thrown
		try{
			customerModel_two.addObserver(new Observer(){
				public void notify(Observable object){

				}
			});
		}catch(Exception e){ fail("An exception was thrown when adding an observer object");
		}

		// Test that a null parameter will throw an exception
		try{
			customerModel_two.addObserver(null);
			fail("An exception was not thrown for null");
		}catch(NullPointerException e){ assertTrue(e instanceof NullPointerException); // This is expected	
		}

		// Create two observers. These are used to test if addObserver and removeObserver do what they're intended to do
		Observer observer1 = new Observer() {

			public void notify(Observable object){

				observer1Fail = false;
			}
		};

		Observer observer2 = new Observer() {

			public void notify(Observable object){

				observer2Fail = false;
			}
		};		

		// Add 2 observers and test
		observer1Fail = true;
		observer2Fail = true;
		customerModel_one.addObserver(observer1);
		customerModel_one.addObserver(observer2);
		customerModel_one.save();
		if(observer1Fail || observer2Fail) fail();

		// Remove one - observer must fail and observer2 succeed or both have been removed
		observer1Fail = true;
		observer2Fail = true;
		customerModel_one.removeObserver(observer1);
		customerModel_one.save();
		if(!observer1Fail || observer2Fail) fail();

		// Remove the remaining observer
		observer1Fail = true;
		observer2Fail = true;
		customerModel_one.removeObserver(observer2);
		customerModel_one.removeObserver(observer2);
		customerModel_one.save();
		if(!observer1Fail || !observer2Fail) fail();
	}
	
	private void testInitHelper(AbstractModel model) throws InvalidAttributeException, MissingDataException{

		// Set attributes
		for(Attribute attribute : model){

			Object value = null;

			if(attribute.getDataType().equalsIgnoreCase("TEXT")) value = "Test!";
			else if(attribute.getDataType().equalsIgnoreCase("INTEGER")) value = new Integer(0);
			else if(attribute.getDataType().equalsIgnoreCase("DATE")) value = new Date(0);
			else if(attribute.getDataType().equalsIgnoreCase("BOOLEAN")) value = new Boolean(true);

			model.setAttribute(attribute.getName(), value);
		}

		// Soon ready for the test...
		model.save();
	}

	/**
	 * Delete the test models from the data layer
	 */
	@After
	public void teardown(){

		// Delete the test models
		customerModel_one.delete();
		customerModel_two.delete();
	}	
}
